#!/usr/bin/env bash
#
# SPDX-License-Identifier: GPL-2.0
#
# Copyright (c) 2013-2023 Igor Pecovnik, igor@armbian.com
#
# This file is a part of the Armbian Build Framework
# https://github.com/armbian/build/

function cli_json_info_pre_run() {
	# "gimme root on a Linux machine"
	cli_standard_relaunch_docker_or_sudo
}

function cli_json_info_run() {
	display_alert "Generating JSON info" "for all boards; wait" "info"

	prep_conf_main_minimal_ni

	# shellcheck disable=SC2317
	function json_info_logged() { # logging wrapper
		LOG_SECTION="json_info" do_with_logging json_info_only
	}

	# shellcheck disable=SC2317
	function json_info_only() {
		prepare_python_and_pip # requires HOSTRELEASE

		declare INFO_TOOLS_DIR="${SRC}"/lib/tools/info

		display_alert "Here we go" "generating JSON info :: ${ARMBIAN_COMMAND} " "info"

		# Targets inventory. Will do all-by-all if no targets file is provided.
		declare TARGETS_FILE="${TARGETS_FILE-"${USERPATCHES_PATH}/${TARGETS_FILENAME:-"targets.yaml"}"}"

		declare BASE_INFO_OUTPUT_DIR="${SRC}/output/info" # Output dir for info

		if [[ "${CLEAN_INFO:-"yes"}" != "no" ]]; then
			display_alert "Cleaning info output dir" "${BASE_INFO_OUTPUT_DIR}" "info"
			rm -rf "${BASE_INFO_OUTPUT_DIR}"
		fi

		mkdir -p "${BASE_INFO_OUTPUT_DIR}"

		# `gha-template` does not depend on the rest of the info-gatherer, so we can run it first and return.
		if [[ "${ARMBIAN_COMMAND}" == "gha-template" ]]; then
			# If we have userpatches/gha/chunks, run the workflow template utility
			declare user_gha_dir="${USERPATCHES_PATH}/gha"
			declare wf_template_dir="${user_gha_dir}/chunks"
			declare GHA_CONFIG_YAML_FILE="${user_gha_dir}/gha_config.yaml"
			if [[ ! -d "${wf_template_dir}" ]]; then
				exit_with_error "output-gha-workflow-template :: no ${wf_template_dir} directory found"
			fi
			if [[ ! -f "${GHA_CONFIG_YAML_FILE}" ]]; then
				exit_with_error "output-gha-workflow-template :: no ${GHA_CONFIG_YAML_FILE} file found"
			fi

			display_alert "Generating GHA workflow template" "output-gha-workflow-template :: ${wf_template_dir}" "info"
			declare GHA_WORKFLOW_TEMPLATE_OUT_FILE_default="${BASE_INFO_OUTPUT_DIR}/artifact-image-complete-matrix.yml"
			declare GHA_WORKFLOW_TEMPLATE_OUT_FILE="${GHA_WORKFLOW_TEMPLATE_OUT_FILE:-"${GHA_WORKFLOW_TEMPLATE_OUT_FILE_default}"}"
			run_host_command_logged "${PYTHON3_VARS[@]}" "${PYTHON3_INFO[BIN]}" "${INFO_TOOLS_DIR}"/output-gha-workflow-template.py "${GHA_WORKFLOW_TEMPLATE_OUT_FILE}" "${GHA_CONFIG_YAML_FILE}" "${wf_template_dir}" "${MATRIX_ARTIFACT_CHUNKS:-"17"}" "${MATRIX_IMAGE_CHUNKS:-"16"}"

			display_alert "Done with" "gha-template" "info"
			run_tool_batcat "${GHA_WORKFLOW_TEMPLATE_OUT_FILE}"

			display_alert "Templated workflow file" "${GHA_WORKFLOW_TEMPLATE_OUT_FILE}" "ext"

			return 0 # stop here.
		fi

		# debs-to-repo-download is also isolated from the rest. It does depend on the debs-to-repo-info, but that's prepared beforehand in a standard pipeline run.
		if [[ "${ARMBIAN_COMMAND}" == "debs-to-repo-download" ]]; then
			display_alert "Downloading debs" "debs-to-repo-download" "info"
			declare DEBS_TO_REPO_INFO_FILE="${BASE_INFO_OUTPUT_DIR}/debs-to-repo-info.json"
			if [[ ! -f "${DEBS_TO_REPO_INFO_FILE}" ]]; then
				exit_with_error "debs-to-repo-download :: no ${DEBS_TO_REPO_INFO_FILE} file found; did you restore the pipeline artifacts correctly?"
			fi
			declare DEBS_OUTPUT_DIR="${DEB_STORAGE}" # this is different depending if BETA=yes (output/debs-beta) or not (output/debs)
			display_alert "Downloading debs to" "${DEBS_OUTPUT_DIR}" "info"
			export PARALLEL_DOWNLOADS_WORKERS="${PARALLEL_DOWNLOADS_WORKERS}"
			run_host_command_logged mkdir -pv "${DEBS_OUTPUT_DIR}"
			run_host_command_logged "${PYTHON3_VARS[@]}" "${PYTHON3_INFO[BIN]}" "${INFO_TOOLS_DIR}"/download-debs.py "${DEBS_TO_REPO_INFO_FILE}" "${DEBS_OUTPUT_DIR}"

			display_alert "Done with" "debs-to-repo-download" "ext"

			return 0 # stop here.
		fi

		# debs-to-repo-download is also isolated from the rest. It does depend on the debs-to-repo-info, but that's prepared beforehand in a standard pipeline run.
		if [[ "${ARMBIAN_COMMAND}" == "debs-to-repo-reprepro" ]]; then
			display_alert "Generating rerepro publishing script" "debs-to-repo-reprepro" "info"
			declare DEBS_TO_REPO_INFO_FILE="${BASE_INFO_OUTPUT_DIR}/debs-to-repo-info.json"
			if [[ ! -f "${DEBS_TO_REPO_INFO_FILE}" ]]; then
				exit_with_error "debs-to-repo-reprepro :: no ${DEBS_TO_REPO_INFO_FILE} file found; did you restore the pipeline artifacts correctly?"
			fi
			declare OUTPUT_INFO_REPREPRO_DIR="${BASE_INFO_OUTPUT_DIR}/reprepro"
			declare OUTPUT_INFO_REPREPRO_CONF_DIR="${OUTPUT_INFO_REPREPRO_DIR}/conf"
			run_host_command_logged mkdir -pv "${OUTPUT_INFO_REPREPRO_DIR}" "${OUTPUT_INFO_REPREPRO_CONF_DIR}"

			# Export params so Python can see them
			export REPO_GPG_KEYID="${REPO_GPG_KEYID}"
			run_host_command_logged "${PYTHON3_VARS[@]}" "${PYTHON3_INFO[BIN]}" "${INFO_TOOLS_DIR}"/repo-reprepro.py "${DEBS_TO_REPO_INFO_FILE}" "${OUTPUT_INFO_REPREPRO_DIR}" "${OUTPUT_INFO_REPREPRO_CONF_DIR}"

			display_alert "Done with" "debs-to-repo-reprepro" "ext"

			return 0 # stop here.
		fi

		### --- inventory --- ###

		declare ALL_USERSPACE_INVENTORY_FILE="${BASE_INFO_OUTPUT_DIR}/all_userspace_inventory.json"
		declare ALL_BOARDS_ALL_BRANCHES_INVENTORY_FILE="${BASE_INFO_OUTPUT_DIR}/all_boards_all_branches.json"
		declare TARGETS_OUTPUT_FILE="${BASE_INFO_OUTPUT_DIR}/all-targets.json"
		declare IMAGE_INFO_FILE="${BASE_INFO_OUTPUT_DIR}/image-info.json"
		declare IMAGE_INFO_CSV_FILE="${BASE_INFO_OUTPUT_DIR}/image-info.csv"
		declare INVENTORY_BOARDS_CSV_FILE="${BASE_INFO_OUTPUT_DIR}/boards-inventory.csv"
		declare REDUCED_ARTIFACTS_FILE="${BASE_INFO_OUTPUT_DIR}/artifacts-reduced.json"
		declare REDUCED_KERNELS_FILE="${BASE_INFO_OUTPUT_DIR}/kernels.ndjson"
		declare REDUCED_KERNELS_DUPLICATE_LINUXCONFIG_FILE="${BASE_INFO_OUTPUT_DIR}/kernels-duplicate-config.json"
		declare REDUCED_UBOOTS_FILE="${BASE_INFO_OUTPUT_DIR}/uboots.ndjson"
		declare ARTIFACTS_INFO_FILE="${BASE_INFO_OUTPUT_DIR}/artifacts-info.json"
		declare ARTIFACTS_INFO_UPTODATE_FILE="${BASE_INFO_OUTPUT_DIR}/artifacts-info-uptodate.json"
		declare OUTDATED_ARTIFACTS_IMAGES_FILE="${BASE_INFO_OUTPUT_DIR}/outdated-artifacts-images.json"

		# Userspace inventory: RELEASES, and DESKTOPS and their possible ARCH'es, names, and support status.
		if [[ ! -f "${ALL_USERSPACE_INVENTORY_FILE}" ]]; then
			display_alert "Generating userspace inventory" "all_userspace_inventory.json" "info"
			run_host_command_logged "${PYTHON3_VARS[@]}" "${PYTHON3_INFO[BIN]}" "${INFO_TOOLS_DIR}"/userspace-inventory.py ">" "${ALL_USERSPACE_INVENTORY_FILE}"
		fi

		# Board/branch inventory.
		if [[ ! -f "${ALL_BOARDS_ALL_BRANCHES_INVENTORY_FILE}" ]]; then
			display_alert "Generating board/branch inventory" "all_boards_all_branches.json" "info"
			run_host_command_logged "${PYTHON3_VARS[@]}" "${PYTHON3_INFO[BIN]}" "${INFO_TOOLS_DIR}"/board-inventory.py ">" "${ALL_BOARDS_ALL_BRANCHES_INVENTORY_FILE}"
		fi

		if [[ "${ARMBIAN_COMMAND}" == "inventory" ]]; then
			display_alert "Done with" "inventory" "info"
			return 0
		fi

		# if TARGETS_FILE does not exist, one will be provided for you, from a template.
		if [[ ! -f "${TARGETS_FILE}" ]]; then
			declare TARGETS_TEMPLATE="${TARGETS_TEMPLATE:-"targets-default.yaml"}"
			display_alert "No targets file found" "using default targets template ${TARGETS_TEMPLATE}" "warn"
			TARGETS_FILE="${SRC}/config/templates/${TARGETS_TEMPLATE}"
		else
			display_alert "Using targets file" "${TARGETS_FILE}" "warn"
		fi

		if [[ ! -f "${TARGETS_OUTPUT_FILE}" ]]; then
			display_alert "Generating targets inventory" "targets-compositor" "info"
			export TARGETS_BETA="${BETA}"                             # Read by the Python script, and injected into every target as "BETA=" param.
			export TARGETS_REVISION="${REVISION}"                     # Read by the Python script, and injected into every target as "REVISION=" param.
			export TARGETS_FILTER_INCLUDE="${TARGETS_FILTER_INCLUDE}" # Read by the Python script; used to "only include" targets that match the given string.
			run_host_command_logged "${PYTHON3_VARS[@]}" "${PYTHON3_INFO[BIN]}" "${INFO_TOOLS_DIR}"/targets-compositor.py "${ALL_BOARDS_ALL_BRANCHES_INVENTORY_FILE}" "${ALL_USERSPACE_INVENTORY_FILE}" "${TARGETS_FILE}" ">" "${TARGETS_OUTPUT_FILE}"
			unset TARGETS_BETA
			unset TARGETS_REVISION
			unset TARGETS_FILTER_INCLUDE
		fi

		if [[ "${ARMBIAN_COMMAND}" == "targets-composed" ]]; then
			display_alert "Done with" "targets-dashboard" "info"
			return 0
		fi

		### Images.

		# The image info extractor.
		if [[ ! -f "${IMAGE_INFO_FILE}" ]]; then
			display_alert "Generating image info" "info-gatherer-image" "info"
			run_host_command_logged "${PYTHON3_VARS[@]}" "${PYTHON3_INFO[BIN]}" "${INFO_TOOLS_DIR}"/info-gatherer-image.py "${TARGETS_OUTPUT_FILE}" ">" "${IMAGE_INFO_FILE}"
		fi

		# convert image info output to CSV for easy import into Google Sheets etc
		if [[ ! -f "${IMAGE_INFO_CSV_FILE}" ]]; then
			display_alert "Generating CSV info" "info.csv" "info"
			run_host_command_logged "${PYTHON3_VARS[@]}" "${PYTHON3_INFO[BIN]}" "${INFO_TOOLS_DIR}"/json2csv.py "<" "${IMAGE_INFO_FILE}" ">" ${IMAGE_INFO_CSV_FILE}
		fi

		if [[ "${ARMBIAN_COMMAND}" == "inventory-boards" ]]; then
			display_alert "Preparing" "inventory-boards" "info"
			run_host_command_logged "${PYTHON3_VARS[@]}" "${PYTHON3_INFO[BIN]}" "${INFO_TOOLS_DIR}"/inventory-boards-csv.py "<" "${IMAGE_INFO_FILE}" ">" ${INVENTORY_BOARDS_CSV_FILE}
			display_alert "Done with" "inventory-boards: ${INVENTORY_BOARDS_CSV_FILE}" "info"
			exit 0
		fi

		if [[ "${ARMBIAN_COMMAND}" == "targets-dashboard" ]]; then
			display_alert "To load the OpenSearch dashboards:" "
				pip3 install opensearch-py # install needed lib to talk to OpenSearch
				sysctl -w vm.max_map_count=262144 # raise limited needed by OpenSearch
				docker-compose --file tools/dashboards/docker-compose-opensearch.yaml up -d # start up OS in docker-compose
				python3 lib/tools/index-opensearch.py < output/info/image-info.json # index the JSON into OpenSearch
				# go check out http://localhost:5601
				docker-compose --file tools/dashboards/docker-compose-opensearch.yaml down # shut down OpenSearch when you're done
				" "info"
			display_alert "Done with" "targets-dashboard" "info"
			return 0
		fi

		### Artifacts.

		# Reducer: artifacts.
		if [[ ! -f "${REDUCED_ARTIFACTS_FILE}" ]]; then
			display_alert "Reducing info into artifacts" "artifact-reducer" "info"
			run_host_command_logged "${PYTHON3_VARS[@]}" "${PYTHON3_INFO[BIN]}" "${INFO_TOOLS_DIR}"/artifact-reducer.py "${IMAGE_INFO_FILE}" ">" "${REDUCED_ARTIFACTS_FILE}"

			# Simple jq to get reduced kernels, with board and branch coordinates and number of images for each; NDJSON (newline-delimited JSON) format.
			jq -c '.[] | select(.artifact_name == "kernel") | {"vars": .original_inputs.vars,"kernel":.inputs.LINUXFAMILY,"needed_by":.needed_by,"ARMBIAN_KERNEL_DEB_NAME":.inputs.ARMBIAN_KERNEL_DEB_NAME,"LINUXCONFIG":.inputs.LINUXCONFIG,"KERNELSOURCE":.inputs.KERNELSOURCE,"KERNELBRANCH":.inputs.KERNELBRANCH} | {"BOARD":.vars.BOARD,"BRANCH":.vars.BRANCH,"kernel":.kernel,"needed_by":.needed_by,"ARMBIAN_KERNEL_DEB_NAME":.ARMBIAN_KERNEL_DEB_NAME,"LINUXCONFIG":.LINUXCONFIG,"KERNELSOURCE":.KERNELSOURCE,"KERNELBRANCH":.KERNELBRANCH}' < "${REDUCED_ARTIFACTS_FILE}" > "${REDUCED_KERNELS_FILE}"

			# Similar, but for u-boot's.
			jq -c '.[] | select(.artifact_name == "uboot") | {"vars": .original_inputs.vars,"needed_by":.needed_by} | {"BOARD":.vars.BOARD,"BRANCH":.vars.BRANCH,"needed_by":.needed_by}' < "${REDUCED_ARTIFACTS_FILE}" > "${REDUCED_UBOOTS_FILE}"

			# Kernels: find duplicate LINUXCONFIG's across the kernels, which is a mistake. Each LINUXFAMILY should have its own LINUXCONFIG, otherwise rewrites will go insane.
			display_alert "Checking for duplicate LINUXCONFIG's across kernels" "kernel-dup-linuxconfig-check" "info"
			jq -s 'to_entries | map(.value) | group_by(.LINUXCONFIG) | map(select(length > 1)) | map({LINUXCONFIG: .[0].LINUXCONFIG,duplicates: map({BOARD, ARMBIAN_KERNEL_DEB_NAME, BRANCH, KERNELSOURCE, KERNELBRANCH})})' "${REDUCED_KERNELS_FILE}" > "${REDUCED_KERNELS_DUPLICATE_LINUXCONFIG_FILE}"

			# if "${REDUCED_KERNELS_DUPLICATE_LINUXCONFIG_FILE}" is larger than 3 bytes, we have duplicates; spit an error (don't exit)
			if [[ $(stat -c%s "${REDUCED_KERNELS_DUPLICATE_LINUXCONFIG_FILE}") -gt 3 ]]; then
				display_alert "Duplicate LINUXCONFIG's found!" "See ${REDUCED_KERNELS_DUPLICATE_LINUXCONFIG_FILE} for details" "err"
				run_host_command_logged jq -C '.' "${REDUCED_KERNELS_DUPLICATE_LINUXCONFIG_FILE}"
			else
				display_alert "No duplicate LINUXCONFIG's found" "all good" "info"
			fi
		fi

		if [[ "${ARMBIAN_COMMAND}" == "inventory-artifacts" ]]; then
			display_alert "Done with" "inventory-artifacts" "info"
			return 0
		fi

		# The artifact info extractor.
		if [[ ! -f "${ARTIFACTS_INFO_FILE}" ]]; then
			display_alert "Generating artifact info" "info-gatherer-artifact" "info"
			run_host_command_logged "${PYTHON3_VARS[@]}" "${PYTHON3_INFO[BIN]}" "${INFO_TOOLS_DIR}"/info-gatherer-artifact.py "${REDUCED_ARTIFACTS_FILE}" ">" "${ARTIFACTS_INFO_FILE}"
		fi

		# Now a mapper, check each OCI coordinate to see if it's up-to-date or not. _cache_ (eternally) the positives, but _never_ cache the negatives.
		# This should ideally use the authentication info and other stuff that ORAS.land would.
		# this is controlled by "CHECK_OCI=yes". most people are not interested in what is or not in the cache when generating a build plan, and it is slow to do.
		if [[ ! -f "${ARTIFACTS_INFO_UPTODATE_FILE}" ]]; then
			display_alert "Gathering OCI info" "mapper-oci-uptodate :: real lookups (CHECK_OCI): ${CHECK_OCI:-"no"}" "info"
			run_host_command_logged "${PYTHON3_VARS[@]}" "${PYTHON3_INFO[BIN]}" "${INFO_TOOLS_DIR}"/mapper-oci-uptodate.py "${ARTIFACTS_INFO_FILE}" "${CHECK_OCI:-"no"}" ">" "${ARTIFACTS_INFO_UPTODATE_FILE}"
		fi

		# A combinator/reducer: image + artifact; outdated artifacts plus the images that depend on them.
		if [[ ! -f "${OUTDATED_ARTIFACTS_IMAGES_FILE}" ]]; then
			display_alert "Combining image and artifact info" "outdated-artifact-image-reducer" "info"
			run_host_command_logged "${PYTHON3_VARS[@]}" "${PYTHON3_INFO[BIN]}" "${INFO_TOOLS_DIR}"/outdated-artifact-image-reducer.py "${ARTIFACTS_INFO_UPTODATE_FILE}" "${IMAGE_INFO_FILE}" ">" "${OUTDATED_ARTIFACTS_IMAGES_FILE}"
		fi

		if [[ "${ARMBIAN_COMMAND}" == "targets" ]]; then
			display_alert "Done with" "targets" "info"
			return 0
		fi

		### CI/CD Outputs.

		# output stage: deploy debs to repo.
		# Artifacts-to-repo output. Takes all artifacts, and produces info necessary for:
		# 1) getting the artifact from OCI only (not build it)
		# 2) getting the list of .deb's to be published to the repo for that artifact
		display_alert "Generating deb-to-repo JSON output" "output-debs-to-repo-json" "info"
		# This produces debs-to-repo-info.json
		run_host_command_logged "${PYTHON3_VARS[@]}" "${PYTHON3_INFO[BIN]}" "${INFO_TOOLS_DIR}"/output-debs-to-repo-json.py "${BASE_INFO_OUTPUT_DIR}" "${OUTDATED_ARTIFACTS_IMAGES_FILE}"
		if [[ "${ARMBIAN_COMMAND}" == "debs-to-repo-json" ]]; then
			display_alert "Done with" "output-debs-to-repo-json" "ext"
			return 0
		fi

		# Output stage: GHA simplest possible two-matrix worflow.
		# A prepare job running this, prepares two matrixes:
		# One for artifacts. One for images.
		# If the image or artifact is up-to-date, it is still included in matrix, but the job is skipped.
		# If any of the matrixes is bigger than 255 items, an error is generated.
		if [[ "${ARMBIAN_COMMAND}" == "gha-matrix" ]]; then
			if [[ "${CLEAN_MATRIX:-"yes"}" != "no" ]]; then
				display_alert "Cleaning GHA matrix output" "clean-matrix" "info"
				run_host_command_logged rm -fv "${BASE_INFO_OUTPUT_DIR}"/gha-*-matrix.json
			fi

			display_alert "Generating GHA matrix for artifacts" "output-gha-matrix :: artifacts" "info"
			declare GHA_ALL_ARTIFACTS_JSON_MATRIX_FILE="${BASE_INFO_OUTPUT_DIR}/gha-all-artifacts-matrix.json"
			if [[ ! -f "${GHA_ALL_ARTIFACTS_JSON_MATRIX_FILE}" ]]; then
				run_host_command_logged "${PYTHON3_VARS[@]}" "${PYTHON3_INFO[BIN]}" "${INFO_TOOLS_DIR}"/output-gha-matrix.py artifacts "${OUTDATED_ARTIFACTS_IMAGES_FILE}" "${MATRIX_ARTIFACT_CHUNKS}" ">" "${GHA_ALL_ARTIFACTS_JSON_MATRIX_FILE}"
			fi

			# rpardini: disabled; we're using a chunked version now, and the GH output is directly set by the Python script, not here.
			# github_actions_add_output "artifact-matrix" "$(cat "${GHA_ALL_ARTIFACTS_JSON_MATRIX_FILE}")"

			display_alert "Generating GHA matrix for images" "output-gha-matrix :: images" "info"
			declare GHA_ALL_IMAGES_JSON_MATRIX_FILE="${BASE_INFO_OUTPUT_DIR}/gha-all-images-matrix.json"
			if [[ ! -f "${GHA_ALL_IMAGES_JSON_MATRIX_FILE}" ]]; then
				# export env vars used by the Python script.
				export SKIP_IMAGES="${SKIP_IMAGES:-"no"}"
				export IMAGES_ONLY_OUTDATED_ARTIFACTS="${IMAGES_ONLY_OUTDATED_ARTIFACTS:-"no"}"
				run_host_command_logged "${PYTHON3_VARS[@]}" "${PYTHON3_INFO[BIN]}" "${INFO_TOOLS_DIR}"/output-gha-matrix.py images "${OUTDATED_ARTIFACTS_IMAGES_FILE}" "${MATRIX_IMAGE_CHUNKS}" ">" "${GHA_ALL_IMAGES_JSON_MATRIX_FILE}"
			fi

			# rpardini: disabled; we're using a chunked version now, and the GH output is directly set by the Python script, not here.
			# github_actions_add_output "image-matrix" "$(cat "${GHA_ALL_IMAGES_JSON_MATRIX_FILE}")"
		fi

		### a secondary stage, which only makes sense to be run inside GHA, and as such should be split in a different CLI or under a flag.
		if [[ "${ARMBIAN_COMMAND}" == "gha-workflow" ]]; then
			# GHA Workflow output. A delusion. Maybe.
			display_alert "Generating GHA workflow" "output-gha-workflow :: complete" "info"
			declare GHA_WORKFLOW_FILE="${BASE_INFO_OUTPUT_DIR}/gha-workflow.yaml"
			run_host_command_logged "${PYTHON3_VARS[@]}" "${PYTHON3_INFO[BIN]}" "${INFO_TOOLS_DIR}"/output-gha-workflow.py "${OUTDATED_ARTIFACTS_IMAGES_FILE}" "${GHA_WORKFLOW_FILE}"
		fi

	}

	do_with_default_build json_info_logged

}
